import { Stage } from 'babylon-lib/base/stage';
import { Utils } from 'babylon-lib/tool/utils';
import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
import { Quaternion } from '@babylonjs/core/Maths/math.vector';
import { AssetManager } from '../assets/asset-manager';
import { Helper } from 'babylon-lib/tool/helper';
import { InstantiatedEntries } from '@babylonjs/core';
import { CombineRig } from '../skinned-animate/combine-rig';
import { CombineRigGroup } from '../skinned-animate/combine-rig-group';
import { CreateHandleTo, IHandle, RemoveHandle } from 'babylon-lib/data/data-handle';


/**
 * 可交互对象
 * 分类
 *  1.角色类
 *  2.环境类
 *  3.道具类
 * 通用
 *  1.缩略图
 *  2.模型动画
 *  3.变换
 * 编辑行为
 *  1.设置变换（通用）
 *  2.属性修改
 * 预览行为
 *  1.交互方式（按键、点击、距离）
 *  2.交互结果
 */


/**
 * @param id - 唯一标识符
 * @param name - 名称
 * @param thumbUrl - 缩略图地址
 * @param modelUrl - 模型地址数组
 */
interface IAbstractInteractiveObjectOptions {
    id?: string,
    name?: string,
    thumbUrl?: string,
    modelUrl?: Array<string>,
}

interface IAbstractInteractiveObjectData {
    id: string,
    name: string,
    position: [number, number, number],
    rotation: [number, number, number, number],
    scale: [number, number, number],
    thumbUrl: string,
    modelUrl: Array<string>,
    isVisible: boolean,
}

/**
 * 可交互对象基类
 * 使用静态 create方法创建实例
 */
abstract class AbstractInteractiveObject {

    /**
     * 创建可交互对象实例
     * @template T - 可交互对象类型
     * @template O - 可交互对象选项类型
     * @param this - 类构造函数
     * @param stage - Stage 实例 
     * @param options - 可交互对象选项
     * @returns - 可交互对象实例
     */
    public static async create<T extends AbstractInteractiveObject, O extends IAbstractInteractiveObjectOptions>(
        this: new (stage: Stage, options?: O) => T, stage: Stage, options?: O
    ): Promise<T> {
        const instance = new this(stage, options);
        await instance.initialize();
        return instance;
    }

    public readonly stage: Stage;
    public readonly container: TransformNode;

    public id: string;
    public name: string;

    protected options: IAbstractInteractiveObjectOptions;
    protected model: Array<InstantiatedEntries | undefined | null> = [];
    protected combineRig: Array<CombineRig | undefined | null> = [];
    protected combineRigGroup = new CombineRigGroup();

    private _thumbUrl: string;
    private _modelUrl: Array<string>;
    private _isVisible: boolean = true;
    private _visibleHandles: IHandle<boolean>[] = [];

    constructor(stage: Stage, options: IAbstractInteractiveObjectOptions = {}) {
        this.stage = stage;
        this.options = options;
        this.id = this.options.id === undefined ? Utils.id + '' : this.options.id;
        this.name = this.options.name || this.id;
        this.container = new TransformNode(this.id + '-container', stage.mainScene);
        this.container.rotationQuaternion = Quaternion.Identity();

        this._thumbUrl = this.options.thumbUrl || '';
        this._modelUrl = this.options.modelUrl || [];
    }

    /**
     * 异步初始化（加载资源，初始化组件）
     */
    protected async initialize(): Promise<void> {
        await this.loadModel();
    }

    protected async loadModel(): Promise<void> {
        for (let i = 0; i < this._modelUrl.length; i++) {
            const url = this._modelUrl[i];
            this.model[i] = null;
            if (!url) continue;
            const model = await AssetManager.fetchInstance(url, this.stage, undefined, undefined, (err) => {
                console.error('加载' + url + '时遇到了错误', err);
            });
            if (!model) continue;

            const ist = model.assetContainer.instantiateModelsToScene((sn) => { return this.name + sn }, false, { doNotInstantiate: false });
            ist.rootNodes.forEach(n => {
                n.parent = this.container;
                Helper.getChildMeshes(n as TransformNode).forEach(m => {
                    m.isVisible = true;
                    m.alwaysSelectAsActiveMesh = true;
                    m.isPickable = false;
                });
            });
            this.model[i] = ist;
        }
        this.updateSkin();
    }

    private updateSkin() {
        for (let i = 0; i < this.model.length; i++) {
            const md = this.model[i];
            this.combineRig[i] = null;
            if (!md) continue;
            if (!md.animationGroups.length) continue;

            const cr = new CombineRig(this.name + 'CombineRig' + i, {
                animationGroup: md.animationGroups[0],
            });
            this.combineRig[i] = cr;
            this.combineRigGroup.addRig(cr);
        }
        const tmp: CombineRig[] = [];
        for (const cr of this.combineRig) {
            if (!cr) continue;
            tmp.push(cr);
            this.model.forEach(m => {
                if (m) {
                    cr.addSkeleton(...m.skeletons);
                }
            });
        }
    }

    public thumbUrl(): string {
        return this._thumbUrl;
    }

    public onVisibleChange(func: { (isVisible: boolean): void }): IHandle<boolean> {
        return CreateHandleTo<boolean>(func, this._visibleHandles);
    }

    public removeVisibleChangeHandle(handle: number | IHandle<boolean>): void {
        RemoveHandle(handle, this._visibleHandles);
    }


    public set isVisible(isVisible: boolean) {
        this.model.forEach(m => {
            if (m) {
                m.rootNodes.forEach(n => {
                    Helper.getChildMeshes(n as TransformNode).forEach(m => {
                        m.isVisible = isVisible;
                    });
                });
            }
        });
        this._isVisible = isVisible;
        this._visibleHandles.forEach(handle => { handle.callback(isVisible); });
    }

    public get isVisible(): boolean {
        return this._isVisible;
    }

    public getData(): IAbstractInteractiveObjectData {
        const data = {} as IAbstractInteractiveObjectData;
        data.id = this.id;
        data.name = this.name;
        const pos = this.container.position;
        data.position = [pos.x, pos.y, pos.z];
        const rot = this.container.rotationQuaternion!;
        data.rotation = [rot.x, rot.y, rot.z, rot.w];
        const scl = this.container.scaling;
        data.scale = [scl.x, scl.y, scl.z];
        data.thumbUrl = this._thumbUrl;
        data.modelUrl = this._modelUrl;
        data.isVisible = this._isVisible;
        return data;
    }

    public setData(data: IAbstractInteractiveObjectData): void {
        this.id = data.id;
        this.name = data.name;
        this.container.position.set(...data.position);
        this.container.rotationQuaternion!.set(...data.rotation);
        this.container.scaling.set(...data.scale);
        this._thumbUrl = data.thumbUrl;
        this._modelUrl = data.modelUrl;
        this.isVisible = data.isVisible;
    }

}


export { AbstractInteractiveObject };
export type {
    IAbstractInteractiveObjectOptions,
    IAbstractInteractiveObjectData,
};